// Copyright 2019 Rover Robotics via Dan Rose
//
// Copyleft 2024, Black Sesame Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "TypeSupport2.hpp"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "dcutils/error_handling.h"
#include "dal_msg/dalidl_runtime_c/message_type_support_struct.h"
#include "dal_msg/dalidl_runtime_c/service_type_support_struct.h"

namespace dal_cyclonedds_cpp
{
class DALIDLC_StructValueType : public StructValueType
{
  const dalidl_typesupport_introspection_c__MessageMembers * impl;
  std::vector<Member> m_members;
  std::vector<std::unique_ptr<const AnyValueType>> m_inner_value_types;

  template<typename ConstructedType, typename ... Args>
  ConstructedType * make_value_type(Args && ... args)
  {
    auto unique_ptr = std::make_unique<ConstructedType>(std::forward<Args>(args)...);
    auto ptr = unique_ptr.get();
    m_inner_value_types.push_back(std::move(unique_ptr));
    return ptr;
  }

public:
  static constexpr TypeGenerator gen = TypeGenerator::DALIDL_C;
  explicit DALIDLC_StructValueType(const dalidl_typesupport_introspection_c__MessageMembers * impl);
  size_t sizeof_struct() const override {return impl->size_of_;}
  size_t n_members() const override {return impl->member_count_;}
  const Member * get_member(size_t index) const override {return &m_members.at(index);}
};

class DALIDLCPP_StructValueType : public StructValueType
{
  const dalidl_typesupport_introspection_cpp::MessageMembers * impl;
  std::vector<Member> m_members;
  std::vector<std::unique_ptr<const AnyValueType>> m_inner_value_types;
  template<typename ConstructedType, typename ... Args>
  ConstructedType * make_value_type(Args && ... args)
  {
    auto unique_ptr = std::make_unique<ConstructedType>(std::forward<Args>(args)...);
    auto ptr = unique_ptr.get();
    m_inner_value_types.push_back(std::move(unique_ptr));
    return ptr;
  }

public:
  static constexpr TypeGenerator gen = TypeGenerator::DALIDL_Cpp;
  explicit DALIDLCPP_StructValueType(
    const dalidl_typesupport_introspection_cpp::MessageMembers * impl);
  size_t sizeof_struct() const override {return impl->size_of_;}
  size_t n_members() const override {return impl->member_count_;}
  const Member * get_member(size_t index) const final {return &m_members.at(index);}
};

std::unique_ptr<StructValueType> make_message_value_type(const dalidl_message_type_support_t * mts)
{
  if (auto ts_c =
    get_message_typesupport_handle(
      mts,
      TypeGeneratorInfo<TypeGenerator::DALIDL_C>::get_identifier()))
  {
    auto members = static_cast<const MetaMessage<TypeGenerator::DALIDL_C> *>(ts_c->data);
    return std::make_unique<DALIDLC_StructValueType>(members);
  } else {
    dcutils_error_string_t prev_error_string = dcutils_get_error_string();
    dcutils_reset_error();

    if (auto ts_cpp =
      get_message_typesupport_handle(
        mts,
        TypeGeneratorInfo<TypeGenerator::DALIDL_Cpp>::get_identifier()))
    {
      auto members = static_cast<const MetaMessage<TypeGenerator::DALIDL_Cpp> *>(ts_cpp->data);
      return std::make_unique<DALIDLCPP_StructValueType>(members);
    } else {
      dcutils_error_string_t error_string = dcutils_get_error_string();
      dcutils_reset_error();

      throw std::runtime_error(
              std::string("Type support not from this implementation.  Got:\n") +
              "    " + prev_error_string.str + "\n" +
              "    " + error_string.str + "\n" +
              "while fetching it");
    }
  }
}

std::pair<std::unique_ptr<StructValueType>, std::unique_ptr<StructValueType>>
make_request_response_value_types(const dalidl_service_type_support_t * svc_ts)
{
  if (auto tsc =
    get_service_typesupport_handle(
      svc_ts,
      TypeGeneratorInfo<TypeGenerator::DALIDL_C>::get_identifier()))
  {
    auto typed =
      static_cast<const TypeGeneratorInfo<TypeGenerator::DALIDL_C>::MetaService *>(tsc->data);
    return {
      std::make_unique<DALIDLC_StructValueType>(typed->request_members_),
      std::make_unique<DALIDLC_StructValueType>(typed->response_members_)
    };
  } else {
    dcutils_error_string_t prev_error_string = dcutils_get_error_string();
    dcutils_reset_error();

    if (auto tscpp =
      get_service_typesupport_handle(
        svc_ts,
        TypeGeneratorInfo<TypeGenerator::DALIDL_Cpp>::get_identifier()))
    {
      auto typed =
        static_cast<const TypeGeneratorInfo<TypeGenerator::DALIDL_Cpp>::MetaService *>(tscpp->data);
      return {
        std::make_unique<DALIDLCPP_StructValueType>(typed->request_members_),
        std::make_unique<DALIDLCPP_StructValueType>(typed->response_members_)
      };
    } else {
      dcutils_error_string_t error_string = dcutils_get_error_string();
      dcutils_reset_error();

      throw std::runtime_error(
              std::string("Service type support not from this implementation.  Got:\n") +
              "    " + prev_error_string.str + "\n" +
              "    " + error_string.str + "\n" +
              "while fetching it");
    }
  }
}

DALIDLC_StructValueType::DALIDLC_StructValueType(
  const dalidl_typesupport_introspection_c__MessageMembers * impl)
: impl{impl}, m_members{}, m_inner_value_types{}
{
  for (size_t index = 0; index < impl->member_count_; index++) {
    auto member_impl = impl->members_[index];

    const AnyValueType * element_value_type;
    switch (DALIDL_TypeKind(member_impl.type_id_)) {
      case DALIDL_TypeKind::MESSAGE:
        m_inner_value_types.push_back(make_message_value_type(member_impl.members_));
        element_value_type = m_inner_value_types.back().get();
        break;
      case DALIDL_TypeKind::STRING:
        element_value_type = make_value_type<DALIDLC_StringValueType>();
        break;
      case DALIDL_TypeKind::WSTRING:
        element_value_type = make_value_type<DALIDLC_WStringValueType>();
        break;
      default:
        element_value_type =
          make_value_type<PrimitiveValueType>(DALIDL_TypeKind(member_impl.type_id_));
        break;
    }

    const AnyValueType * member_value_type;
    if (!member_impl.is_array_) {
      member_value_type = element_value_type;
    } else if (member_impl.array_size_ != 0 && !member_impl.is_upper_bound_) {
      member_value_type = make_value_type<ArrayValueType>(
        element_value_type, member_impl.array_size_);
    } else if (member_impl.size_function) {
      member_value_type = make_value_type<CallbackSpanSequenceValueType>(
        element_value_type, member_impl.size_function, member_impl.get_const_function);
    } else {
      member_value_type = make_value_type<DALIDLC_SpanSequenceValueType>(element_value_type);
    }
    m_members.push_back(
      Member{
        member_impl.name_,
        member_value_type,
        member_impl.offset_,
      });
  }
}

DALIDLCPP_StructValueType::DALIDLCPP_StructValueType(
  const dalidl_typesupport_introspection_cpp::MessageMembers * impl)
: impl(impl)
{
  for (size_t index = 0; index < impl->member_count_; index++) {
    auto member_impl = impl->members_[index];

    const AnyValueType * element_value_type;
    switch (DALIDL_TypeKind(member_impl.type_id_)) {
      case DALIDL_TypeKind::MESSAGE:
        m_inner_value_types.push_back(make_message_value_type(member_impl.members_));
        element_value_type = m_inner_value_types.back().get();
        break;
      case DALIDL_TypeKind::STRING:
        element_value_type = make_value_type<DALIDLCPP_StringValueType>();
        break;
      case DALIDL_TypeKind::WSTRING:
        element_value_type = make_value_type<DALIDLCPP_U16StringValueType>();
        break;
      default:
        element_value_type =
          make_value_type<PrimitiveValueType>(DALIDL_TypeKind(member_impl.type_id_));
        break;
    }

    const AnyValueType * member_value_type;
    if (!member_impl.is_array_) {
      member_value_type = element_value_type;
    } else if (member_impl.array_size_ != 0 && !member_impl.is_upper_bound_) {
      member_value_type = make_value_type<ArrayValueType>(
        element_value_type, member_impl.array_size_);
    } else if (DALIDL_TypeKind(member_impl.type_id_) == DALIDL_TypeKind::BOOLEAN) {
      member_value_type = make_value_type<BoolVectorValueType>();
    } else {
      member_value_type = make_value_type<CallbackSpanSequenceValueType>(
        element_value_type, member_impl.size_function, member_impl.get_const_function);
    }
    m_members.push_back(
      Member {
        member_impl.name_,
        member_value_type,
        member_impl.offset_,
      });
  }
}
}  // namespace dal_cyclonedds_cpp
